Цепочечные команды

Эти команды также называют командами обработки строк символов. Названия почти синонимичны.
Отличие в том, что под строкой символов здесь понимается последовательность байт, а цепочка — это более общее название для случаев, когда элементы последовательности имеют размер больше байта — слово или двойное слово.
Таким образом, цепочечные команды позволяют проводить действия над блоками памяти, представляющими собой последовательности элементов следующего размера:

Содержимое этих блоков для микропроцессора не имеет никакого значения. Это могут быть символы, числа и все что угодно. Главное, чтобы размерность элементов совпадала с одной из перечисленных и эти элементы находились в соседних ячейках памяти.

Всего в системе команд микропроцессора имеется семь операций-примитивов обработки цепочек.
Каждая из них реализуется в микропроцессоре тремя командами, в свою очередь, каждая из этих команд работает с соответствующим размером элемента — байтом, словом или двойным словом.
Особенность всех цепочечных команд в том, что они, кроме обработки текущего элемента цепочки, осуществляют еще и автоматическое продвижение к следующему элементу данной цепочки.

Перечислим операции-примитивы и команды, с помощью которых они реализуются, а затем подробно их рассмотрим:

Логически к этим командам нужно отнести и так называемые префиксы повторения. Вспомните формат машинной команды и его первые необязательные байты префиксов. Один из возможных типов префиксов — это префиксы повторения. Они предназначены для использования цепочечными командами.

Префиксы повторения имеют свои мнемонические обозначения:

rep
repe или repz
repne или repnz

Эти префиксы повторения указываются перед нужной цепочечной командой в поле метки.
Цепочечная команда без префикса выполняется один раз. Размещение префикса перед цепочечной командой заставляет ее выполняться в цикле.

Отличия приведенных префиксов в том, на каком основании принимается решение о циклическом выполнении цепочечной команды: по состоянию регистра ecx/cx или по флагу нуля zf:

Следующий важный момент, связанный с цепочечными командами, заключается в особенностях формирования физического адреса операндов адрес_источника и адрес_приемника.
Цепочка-источник, адресуемая операндом адрес_источника, может находиться в текущем сегменте данных, определяемом регистром ds.
Цепочка-приемник, адресуемая операндом адрес_приемника, должна быть в дополнительном сегменте данных, адресуемом сегментным регистром es.
Важно отметить, что допускается замена (с помощью префикса замены сегмента) только регистра ds, регистр es подменять нельзя.
Вторые части адресов - смещения цепочек — также находятся в строго определенных местах.
Для цепочки-источника это регистр esi/si (Source Index register — индексный регистр источника).
Для цепочки-получателя это регистр edi/di (Destination Index register - индексный регистр приемника).
Таким образом, полные физические адреса для операндов цепочечных команд следующие: Вы, наверное обратили внимание на то, что все семь групп команд, реализующих цепочечные операции-примитивы, имеют похожий по структуре набор команд.
В каждом из этих наборов присутствует одна команда с явным указанием операндов и три команды, не имеющие операндов.
На самом деле, набор команд микропроцессора имеет соответствующие машинные команды только для цепочечных команд ассемблера без операндов. Команды с операндами транслятор ассемблера использует только для определения типов операндов.
После того как выяснен тип элементов цепочек по их описанию в памяти, генерируется одна из трех машинных команд для каждой из цепочечных операций. По этой причине все регистры, содержащие адреса цепочек, должны быть инициализированы заранее, в том числе и для команд, допускающих явное указание операндов.
В силу того, что цепочки адресуются однозначно, нет особого смысла применять команды с операндами. Главное, что вы должны запомнить, — правильная загрузка регистров указателями обязательно требуется до выдачи любой цепочечной команды.

Последний важный момент, касающийся всех цепочечных команд, — это направление обработки цепочки. Есть две возможности:

Как мы увидим ниже, цепочечные команды сами выполняют модификацию регистров, адресующих операнды, обеспечивая тем самым автоматическое продвижение по цепочке. Количество байт, на которые эта модификация осуществляется, определяется кодом команды. А вот знак этой модификации определяется значением флага направления df (Direction Flag) в регистре eflags/flags: Состоянием флага df можно управлять с помощью двух команд, не имеющих операндов:
cld (Clear Direction Flag) — очистить флаг направления. Команда сбрасывает флаг направления df в 0.
std (Set Direction Flag) — установить флаг направления. Команда устанавливает флаг направления df в 1.

Это вся информация, касающаяся общих свойств цепочечных команд. Далее мы более подробно рассмотрим каждую операцию-примитив и команды, которые ее реализуют. При этом более подробно мы будем рассматривать одну команду в каждой группе цепочечных команд — команду с операндами. Это будет делаться из тех соображений, что это более общая команда в смысле ограничений, накладываемых на типы операндов. 

Операция пересылки цепочек

Команды, реализующие эту операцию-примитив, производят копирование элементов из одной области памяти (цепочки) в другую. Размер элемента определяется применяемой командой.

Система команд TASM предоставляет программисту четыре команды, работающие с разными размерами элементов цепочки:

movs адрес_приемника,адрес_источника (MOVe String) — переслать цепочку;
movsb (MOVe String Byte) — переслать цепочку байт;
movsw (MOVe String Word) — переслать цепочку слов;
movsd (MOVe String Double word) — переслать цепочку двойных слов.

Команда movs:

movs адрес_приемника,адрес_источника
Команда копирует байт, слово или двойное слово из цепочки, адресуемой операндом адрес_источника, в цепочку, адресуемую операндом адрес_приемника.
Размер пересылаемых элементов ассемблер определяет, исходя из атрибутов идентификаторов, указывающих на области памяти приемника и источника. К примеру, если эти идентификаторы были определены директивой db, то пересылаться будут байты, если идентификаторы были определены с помощью директивы dd, то пересылке подлежат 32-битовые элементы, то есть двойные слова.
Ранее уже было отмечено, что для цепочечных команд с операндами, к которым относится и команда пересылки movs адрес_приемника,адрес_источника, не существует машинного аналога.
При трансляции в зависимости от типа операндов транслятор преобразует ее в одну из трех машинных команд:
movsb, movsw или movsd.

Сама по себе команда movs пересылает только один элемент, исходя из его типа, и модифицирует значения регистров esi/si и edi/di. Если перед командой написать префикс rep, то одной командой можно переслать до 64 Кбайт данных (если размер адреса в сегменте 16 бит — use16) или до 4 Гбайт данных (если размер адреса в сегменте 32 бит - use32).
Число пересылаемых элементов должно быть загружено в счетчик — регистр cx (use16) или ecx (use32).

Перечислим набор действий, которые нужно выполнить в программе для того, чтобы выполнить пересылку последовательности элементов из одной области памяти в другую с помощью команды movs.
В общем случае этот набор действий можно рассматривать как типовой для выполнения любой цепочечной команды:

На примере листинга 1 рассмотрим, как эти действия реализуются программно. В этой программе производится пересылка символов из одной строки в другую. Строки находятся в одном сегменте памяти. Для пересылки используется команда-примитив movs с префиксом повторения rep.
 
Листинг 1 Пересылка строк командой movs
;prg_11_1.asm
MASM
MODEL   small
STACK   256
.data
source  db      'Тестируемая строка','$'
;строка-источник
dest    db      19 DUP (' ')    ;строка-приёмник
.code
        assume  ds:@data,es:@data
main:   ;точка входа в программу
        mov     ax,@data        ;загрузка сегментных регистров
        mov     ds,ax   ;настройка регистров DS и ES
        ;на адрес сегмента данных
        mov     es,ax
        cld     ;сброс флага DF — обработка строки от начала к концу
        lea     si,source       ;загрузка в si смещения строки-источника
        lea     di,dest ;загрузка в DS смещения строки-приёмника
        mov     cx,20   ;для префикса rep — счетчик повторений (длина строки)
rep     movs    dest,source     ;пересылка строки
        lea     dx,dest
        mov     ah,09h  ;вывод на экран строки-приёмника
        int     21h
exit:
        mov     ax,4c00h
        int     21h
end     main

Операция сравнения цепочек

Команды, реализующие эту операцию-примитив, производят сравнение элементов цепочки-источника с элементами цепочки-приемника. Здесь ситуация с набором команд и методами работы с ними аналогична операции-примитиву пересылки цепочек.
TASM предоставляет программисту четыре команды сравнения цепочек, работающие с разными размерами элементов цепочки:
cmps адрес_приемника,адрес_источника(CoMPare String) — сравнить строки;
cmpsb (CoMPare String Byte) — сравнить строку байт;
cmpsw (CoMPare String Word) — сравнить строку слов;
cmpsd (CoMPare String Double word) — сравнить строку двойных слов.

Команда cmps

Синтаксис команды cmps:
cmps адрес_приемника,адрес_источника
Здесь: Алгоритм работы команды cmps заключается в последовательном выполнении вычитания (элемент цепочки-источника — элемент цепочки-получателя) над очередными элементами обеих цепочек.
Принцип выполнения вычитания командой cmps аналогичен команде сравнения cmp. Она, так же, как и cmp, производит вычитание элементов, не записывая при этом результата, и устанавливает флаги zf, sf и of.
После выполнения вычитания очередных элементов цепочек командой cmps, индексные регистры esi/si и edi/di автоматически изменяются в соответствии со значением флага df на значение, равное размеру элемента сравниваемых цепочек.
Чтобы заставить команду cmps выполняться несколько раз, то есть производить последовательное сравнение элементов цепочек, необходимо перед командой cmps определить префикс повторения.
С командой cmps можно использовать префикс повторения repe/repz или repne/repnz: Таким образом, выбрав подходящий префикс, удобно использовать команду cmps для поиска одинаковых или различающихся элементов цепочек.
Выбор префикса определяется причиной, которая приводит к выходу из цикла. Таких причин может быть две для каждого из префиксов. Для определения конкретной причины наиболее подходящим является способ, использующий команду условного перехода jcxz. Ее работа заключается в анализе содержимого регистра ecx/cx, и если оно равно нулю, то управление передается на метку, указанную в качестве операнда jcxz. Так как в регистре ecx/cx содержится счетчик повторений для цепочечной команды, имеющей любой из префиксов повторения, то, анализируя ecx/cx, можно определить причину выхода из зацикливания цепочечной команды. Если значение в ecx/cx не равно нулю, то это означает, что выход произошел по причине совпадения либо несовпадения очередных элементов цепочек.
Существует возможность еще больше конкретизировать информацию о причине, приведшей к окончанию операции сравнения. Сделать это можно с помощью команд условной передачи управления (табл. 1 и 2).

Таблица 1. Сочетание команд условной передачи управления с результатами команды cmps (для чисел со знаком)

Причина прекращения операции сравнения Команда условного перехода, реализующая переход по этой причине
операнд_источник > операнд_приемник jg
операнд_источник = операнд_приемник je
операнд_источник <> операнд_приемник jne
операнд_источник < операнд_приемник jl
операнд_источник <= операнд_приемник jle
операнд_источник >= операнд_приемник jge

Таблица 2. Сочетание команд условной передачи управления с результатами команды cmps (для чисел без знака)

Причина прекращения операции сравнения Команда условного перехода, реализующая переход по этой причине
операнд_источник > операнд_приемник ja
операнд_источник = операнд_приемник je
операнд_источник <> операнд_приемник jne
операнд_источник < операнд_приемник jb
операнд_источник <= операнд_приемник jbe
операнд_источник >= операнд_приемник jae

Как определить местоположение очередных совпавших или не совпавших элементов в цепочках?

Вспомните, что после каждой итерации цепочечная команда автоматически осуществляет инкремент/декремент значения адреса в соответствующих индексных регистрах. Поэтому после выхода из цикла в этих регистрах будут находиться адреса элементов, находящихся в цепочке после (!) элементов, которые послужили причиной выхода из цикла.
Для получения истинного адреса этих элементов необходимо скорректировать содержимое индексных регистров, увеличив либо уменьшив значение в них на длину элемента цепочки.
В качестве примера рассмотрим программу из листинга 2, которая сравнивает две строки, находящиеся в одном сегменте. Используется команда cmps. Префикс повторения - repe.
 
 Листинг 2. Сравнение двух строк командой cmps
<1> ;prg_11_2.asm
<2> MODEL       small
<3> STACK       256
<4> .data
<5> match       db      0ah,0dh,'Строки совпадают.','$'
<6> failed      db      0ah,0dh,'Строки не совпадают','$'
<7> string1     db      '0123456789',0ah,0dh,'$';исследуемые строки
<8> string2     db      '0123406789','$'
<9> .code
<10> ASSUME     ds:@data,es:@data       ;привязка DS и ES к сегменту данных
<11> main:
<12>    mov     ax,@data        ;загрузка сегментных регистров
<13>    mov     ds,ax
<14>    mov     es,ax   ;настройка ES на DS
<15> ;вывод на экран исходных строк string1 и string2
<16>    mov     ah,09h
<17>    lea     dx,string1
<18>    int     21h
<19>    lea     dx,string2
<20>    int     21h
<21> ;сброс флага DF — сравнение в направлении возрастания  адресов
<22>    cld
<23>    lea     si,string1      ;загрузка в si смещения string1
<24>    lea     di,string2      ;загрузка в di смещения string2
<25>    mov     cx,10   ;длина строки для префикса repe
<26> ;сравнение строк (пока сравниваемые элементы строк равны)
<27> ;выход при обнаружении не совпавшего элемента
<28> cycl:
<29>    repe    cmps    string1,string2
<30>    jcxz    equal   ;cx=0, то есть строки совпадают
<31>    jne     not_match       ;если не равны — переход на not_match
<32> equal:             ;иначе, если совпадают, то
<33>    mov     ah,09h  ;вывод сообщения
<34>    lea     dx,match
<35>    int     21h
<36>    jmp     exit    ;выход
<37> not_match:         ;не совпали
<38>    mov     ah,09h
<39>    lea     dx,failed
<40>    int     21h     ;вывод сообщения
<41> ;теперь, чтобы обработать не совпавший элемент в
 строке, необходимо уменьшить значения регистров si и di
<42>    dec     si
<43>    dec     di
<44> ;сейчас в ds:si и es:di адреса несовпавших элементов
<45> ;здесь вставить код по обработке несовпавшего элемента
<46> ;после этого продолжить поиск в строке:
<47>    inc     si
<48>    inc     di
<49>    jmp     cycl
<50> exit:      ;выход
<51>    mov     ax,4c00h
<52>    int     21h
<53> end        main    ;конец программы
Программа достаточно прозрачна, только два момента, на мой взгляд, требуют пояснения: